package xerror

import (
	"github.com/blang/semver/v4"
	"google.golang.org/grpc/codes"
	"net/http"
)

// extensions of gRPC's canonical error code
const (
	// Downgraded 接口已降级
	// HTTP Mapping: 429 Too Many Requests
	Downgraded codes.Code = 50

	// CircuitBreaking 接口被熔断
	// HTTP Mapping: 503 Service Unavailable
	CircuitBreaking codes.Code = 51

	// RateLimited 被限流
	// HTTP Mapping: 429 Too Many Requests
	RateLimited codes.Code = 52

	// SecurityBlocked 因安全原因被限制。比如触发风控、反作弊规则等
	// HTTP Mapping: 403 Forbidden
	SecurityBlocked codes.Code = 53
)

// httpStatusCodeMapping maps error code to http status code
var httpStatusCodeMapping = map[codes.Code]int{
	codes.OK:                 http.StatusOK,
	codes.Canceled:           499, // Client Closed Request，http 包中未定义对应常量
	codes.Unknown:            http.StatusInternalServerError,
	codes.InvalidArgument:    http.StatusBadRequest,
	codes.DeadlineExceeded:   http.StatusGatewayTimeout,
	codes.NotFound:           http.StatusNotFound,
	codes.AlreadyExists:      http.StatusConflict,
	codes.PermissionDenied:   http.StatusForbidden,
	codes.ResourceExhausted:  http.StatusTooManyRequests,
	codes.FailedPrecondition: http.StatusBadRequest,
	codes.Aborted:            http.StatusConflict,
	codes.OutOfRange:         http.StatusBadRequest,
	codes.Unimplemented:      http.StatusNotImplemented,
	codes.Internal:           http.StatusInternalServerError,
	codes.Unavailable:        http.StatusServiceUnavailable,
	codes.DataLoss:           http.StatusInternalServerError,
	codes.Unauthenticated:    http.StatusUnauthorized,
	Downgraded:               http.StatusTooManyRequests,
	CircuitBreaking:          http.StatusServiceUnavailable,
	RateLimited:              http.StatusTooManyRequests,
	SecurityBlocked:          http.StatusForbidden,
}

// MapErrorCodeToHTTPStatusCode returns corresponding http status code for the error code.
// It returns StatusInternalServerError (a.k.k.a 500) if the error code is unknown.
func MapErrorCodeToHTTPStatusCode(c codes.Code) int {
	statusCode, ok := httpStatusCodeMapping[c]
	if !ok {
		return http.StatusInternalServerError
	}
	return statusCode
}

// ErrorCodeMapper map error code to HTTP status code according to config.
type ErrorCodeMapper struct {
	// app => version range supporting non-200 HTTP status code.
	// Palfish apps are identified by integers. See http://confluence.pri.ibanyu.com/pages/viewpage.action?pageId=16614161
	config map[int32]semver.Range
}

// NewErrorCodeMapper create an ErrorCodeMapper instance with minimum versions supporting non-200 HTTP status code.
// minVersions: app => min versions supporting non-200 HTTP status code.
func NewErrorCodeMapper(minVersions map[int32]semver.Version) *ErrorCodeMapper {
	config := map[int32]semver.Range{}
	for app, minVer := range minVersions {
		minVerCopy := minVer
		config[app] = semver.Range(func(version semver.Version) bool {
			if version.GE(minVerCopy) {
				return true
			}
			return false
		})
	}
	return NewErrorCodeMapperWithVersionRange(config)
}

// NewErrorCodeMapperWithVersionRange create an ErrorCodeMapper instance with version ranges supporting non-200 HTTP status code.
// config: app => version range supporting non-200 HTTP status code.
func NewErrorCodeMapperWithVersionRange(config map[int32]semver.Range) *ErrorCodeMapper {
	return &ErrorCodeMapper{config: config}
}

// MapToHTTPStatusCode map error code to HTTP status code.
// It returns corresponding HTTP status code only if `app` is configured and `version` is within the configured version range.
// Otherwise it will return http.StatusOK (a.k.k.a 200).
func (ecm *ErrorCodeMapper) MapToHTTPStatusCode(c codes.Code, app int32, version *semver.Version) int {
	r, ok := ecm.config[app]
	if !ok {
		return http.StatusOK
	}

	if r(*version) {
		return MapErrorCodeToHTTPStatusCode(c)
	} else {
		return http.StatusOK
	}
}
